package cn.z2huo.core.util;

import cn.hutool.core.date.SystemClock;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.z2huo.core.lang.SingletonHolder;
import cn.z2huo.core.lang.Snowflake;
import cn.z2huo.core.lang.id.IdConstants;

import java.time.LocalDateTime;

/**
 * <p>
 *
 * @author z2huo
 */
public final class IdUtils {

    private static final LocalDateTime LOCAL_DATE_TIME = LocalDateTime.of(2024, 12, 2, 12, 25, 43);

    private IdUtils() {
    }

    /**
     * <p>
     * 获取单例的 Twitter 的 Snowflake 算法生成器对象
     *
     * <p>分布式系统中，有一些需要使用全局唯一ID的场景，有些时候我们希望能使用一种简单一些的ID，并且希望ID能够按照时间有序生成。
     *
     * <p>
     * snowflake的结构如下(每部分用-分开):<br>
     *
     * <pre>
     * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
     * </pre>
     *
     * <p>
     * 第一位为未使用，接下来的41位为毫秒级时间(41位的长度可以使用69年)<br>
     * 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点）<br>
     * 最后12位是毫秒内的计数（12位的计数顺序号支持每个节点每毫秒产生4096个ID序号）
     *
     * <p>
     * 参考：<a href="http://www.cnblogs.com/relucent/p/4955340.html">Twitter的分布式自增ID算法snowflake (Java版)</a>
     */
    public static Snowflake getSnowflake() {
        return SingletonHolder.get(Snowflake.class);
    }

    /**
     * 获取单例的 Twitter 的 Snowflake 算法生成器对象
     *
     * @param workerId 终端ID
     */
    public static Snowflake getSnowflake(long workerId) {
        return SingletonHolder.get(Snowflake.class, workerId);
    }

    /**
     * 获取单例的 Twitter 的 Snowflake 算法生成器对象
     *
     * @param workerId     终端ID
     * @param datacenterId 数据中心ID
     */
    public static Snowflake getSnowflake(long workerId, long datacenterId) {
        return SingletonHolder.get(Snowflake.class, workerId, datacenterId);
    }

    /**
     * 获取单例的 Twitter 的 Snowflake 算法生成器对象
     *
     * @param epochDate        初始化时间起点（null 表示默认起始日期）,后期修改会导致 id 重复,如果要修改连 workerId dataCenterId，慎用
     * @param workerId         工作机器节点 id
     * @param dataCenterId     数据中心 id
     * @param isUseSystemClock 是否使用{@link SystemClock} 获取当前时间戳
     */
    public static Snowflake getSnowflake(LocalDateTime epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
        return SingletonHolder.get(Snowflake.class, epochDate, workerId, dataCenterId, isUseSystemClock);
    }

    public static Snowflake getSnowflake(String key) {
        return SingletonHolder.get(key, Snowflake.class);
    }

    public static Snowflake getSnowflake(String key, long workerId) {
        return SingletonHolder.get(key, Snowflake.class, workerId);
    }

    public static Snowflake getSnowflake(String key, long workerId, long datacenterId) {
        return SingletonHolder.get(key, Snowflake.class, workerId, datacenterId);
    }

    public static Snowflake getSnowflake(String key, LocalDateTime epochDate, long workerId, long dataCenterId, boolean isUseSystemClock) {
        return SingletonHolder.get(key, Snowflake.class, epochDate, workerId, dataCenterId, isUseSystemClock);
    }

    /**
     * <p>获取数据中心 ID
     *
     * <p>数据中心 ID 依赖于本地网卡 MAC 地址
     * <p>保证在不同机器上面的 datacenter id 不同
     *
     * <p>此算法来自于mybatis-plus#Sequence
     *
     *
     * @param maxDatacenterId 最大的中心 ID
     * @return 数据中心 ID
     */
    public static long getDataCenterId(long maxDatacenterId) {
        Assert.isTrue(maxDatacenterId > 0, "maxDatacenterId must be > 0");

        if (maxDatacenterId == Long.MAX_VALUE) {
            maxDatacenterId -= 1;
        }
        long id = 1L;
        byte[] mac = null;
        try {
            // 获取 mac 地址的二进制数组
            mac = NetUtil.getLocalHardwareAddress();
        } catch (UtilException ignore) {
            // ignore
        }
        if (mac != null) {
            // 下面这行代码最后向右移动 6 位，前面的计算结果为 16 位，向右移动 6 位，还剩 10 位
            id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;
            // 之后对这 10 位的整数，取余
            id = id % (maxDatacenterId + 1);
        }
        return id;
    }

    /**
     * <p>获取机器 ID，使用进程 ID 配合数据中心 ID 生成
     *
     * <p>机器依赖于本进程 ID 或进程名的 Hash 值。
     * <p>保证在同一个机器上面的不同 Java 实例进程的 work id 不同
     *
     * <p>此算法来自于mybatis-plus#Sequence
     *
     * @param datacenterId 数据中心ID
     * @param maxWorkerId  最大的机器节点ID
     */
    public static long getWorkerId(long datacenterId, long maxWorkerId) {
        final StringBuilder mpid = new StringBuilder();
        mpid.append(datacenterId);
        try {
            mpid.append(RuntimeUtil.getPid());
        } catch (UtilException igonre) {
            //ignore
        }
        // MAC + PID 的 hashcode 获取16个低位
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    /**
     * 简单获取 Snowflake 的 nextId
     *
     * @return nextId
     */
    public static long nextId() {
        return getSnowflake(LOCAL_DATE_TIME, IdConstants.DEFAULT_WORKER_ID, IdConstants.DEFAULT_DATACENTER_ID, true).nextId();
    }

    /**
     * 简单获取 Snowflake 的 nextId
     *
     * @return nextIdStr
     */
    public static String nextIdStr() {
        return getSnowflake(LOCAL_DATE_TIME, IdConstants.DEFAULT_WORKER_ID, IdConstants.DEFAULT_DATACENTER_ID, true).nextIdStr();
    }

}
